package ldk.util.logger;

import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by ldkxingzhe@163.com on 2017/5/27.
 */
public class FileHandler implements Handler{
    private String mDirPath;
    private File mCurrentFile;
    private BufferedOutputStream mPrinter;
    private String mCurrentFilePath;
    private int mCurrentFileSize = 0;
    private long mCurrentFileCreateTime = 0;
    private boolean mIsOpened = false, mIsClosed = false;

    @VisibleForTesting int mLimitSizePerFile = 1000000; // 1000KB
    @VisibleForTesting int mMaxTimePerFile = 60 * 60 * 1000; // one hour
    @VisibleForTesting int mMaxTimeForFileSave = 48 * 60 * 60 * 1000; // 48 hours

    @VisibleForTesting static final String LOG_FILE_PATTERN = "yyyyMMdd_HHmm_ss_SSS";
    @VisibleForTesting static final String LOG_TIME_PATTERN = "HH:mm:ss-SSS";
    @VisibleForTesting static final SimpleDateFormat sLogTimeFormat = new SimpleDateFormat(LOG_TIME_PATTERN);

    public FileHandler(File dirFile){
        mDirPath = dirFile.getAbsolutePath();
        if (!dirFile.exists()){
            dirFile.mkdirs();
        }
    }

    @Override
    public void open() {
        if (!mIsOpened){
            synchronized (FileHandler.class){
                if (!mIsOpened){
                    openNewFile();
                    mIsOpened = true;
                }
            }
        }
    }

    private boolean openNewFile(){
        if (mPrinter != null){
            closeCurrentFile();
        }
        cleanOldLog();
        SimpleDateFormat format = new SimpleDateFormat(LOG_FILE_PATTERN);
        mCurrentFilePath = mDirPath + "/" + format.format(new Date()) + ".log";
        mCurrentFile = new File(mCurrentFilePath);
        File parent = mCurrentFile.getParentFile();
        if (parent != null && !parent.exists()){
            parent.mkdirs();
        }
        mCurrentFileSize = 0;
        try {
            mPrinter = new BufferedOutputStream(new FileOutputStream(mCurrentFile));
            mCurrentFileCreateTime = System.currentTimeMillis();
            return true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        }
    }

    @VisibleForTesting
    void cleanOldLog() {
        File parentFile = new File(mDirPath);
        File[] childrenFileList = parentFile.listFiles();
        if (childrenFileList == null) return;
        SimpleDateFormat fileNameFormat = new SimpleDateFormat(LOG_FILE_PATTERN);
        long currentDateTime = new Date().getTime();
        for (File file : childrenFileList){
            String fileName = file.getName();
            if (!fileName.endsWith(".log")){
                file.delete();
                continue;
            }
            if (file.length() == 0) file.delete();
            String realName = fileName.substring(0, fileName.length() - 4);
            boolean delete;
            try {
                long diff = currentDateTime - fileNameFormat.parse(realName).getTime();
                delete = diff >= mMaxTimeForFileSave;
            } catch (ParseException e) {
                delete = true;
            }
            if (delete) file.delete();
        }
    }

    private void closeCurrentFile(){
        try {
            mPrinter.flush();
            mPrinter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            mPrinter = null;
        }
    }

    @Override
    public void write(int logLevel, @Nullable String tag, @Nullable String lineRecord) {
        if (lineRecord == null) lineRecord = "null";
        if (tag == null) tag = "null";
        lineRecord = lineRecord.replace("\n", "\n\t\t\t");
        String end = String.format("%s %s %s\t\t%s\n", sLogTimeFormat.format(new Date()), LogUtils.logLevel2Str(logLevel), tag, lineRecord);
        try {
            byte[] bytes = end.getBytes("utf-8");
            synchronized (FileHandler.class){
                long timeDiff = System.currentTimeMillis() - mCurrentFileCreateTime;
                int originSize = mCurrentFileSize;
                mCurrentFileSize += bytes.length;
                boolean sizeOutOfBound = (timeDiff >= mMaxTimePerFile || mCurrentFileSize > mLimitSizePerFile) && originSize > 1000;
                if (sizeOutOfBound || mPrinter == null){
                    // need open a new file
                    if (openNewFile()){
                        write(logLevel, tag, lineRecord);
                    }
                }else{
                    mPrinter.write(bytes);
                }
            }
        } catch (Exception e) {
            // ignore
            e.printStackTrace();
        }
    }

    @Override
    public void flush() {
        if (mPrinter != null) try {
            mPrinter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void close() {
        if (mPrinter != null) try {
            mPrinter.flush();
            mPrinter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            mPrinter = null;
            mIsClosed = true;
        }
    }

    @Override
    public boolean isOpened() {
        return mIsOpened;
    }

    @Override
    public boolean isClosed() {
        return mIsClosed;
    }
}
